package GameObjects.Ships;

import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.ActionListener;
import java.util.ArrayList;

import GUI.BottomPanel;
import GUI.Panels.GameObjectPanels.ShipPanels.ShipPanel;
import GameObjects.GameObject;
import GameObjects.InstanceObject;
import GameObjects.Instances.Instances;
import GameObjects.Physics.DeltaPosition;
import GameObjects.Physics.Position;
import GameObjects.Ships.Engines.ShipEngine;
import GameObjects.Ships.HyperDrives.HyperDrive;
import GameObjects.Ships.Reactors.Reactor;
import GameObjects.Ships.Sheilds.Sheilds;
import GameObjects.Ships.Weapon.Weapon;
import GameObjects.Ships.Weapon.Bullets.Bullet;
import Helper.HelperMethods;

public abstract class Ship extends GameObject 
{	
	private ArrayList<Weapon> weapons;
	private double maxWeaponEnergy;
	private double weaponEnergy;
	
	private ArrayList<ShipEngine> engines;
	private double maxEngineEnergy;
	private double engineEnergy;
	
	private HyperDrive hyperDrive;
	private Reactor reactor;
	private Sheilds sheild;
	
	private ShipPanel shipPanel;
	
	private double maxHealth;
	private double health;

	private double speed;
	private double rotateSpeed;
	
	public Ship(Instances instances, Position position, double maxWeaponEnergy, double maxEngineEnergy) 
	{
		super(instances, position, 0, 0);
		this.maxWeaponEnergy = maxWeaponEnergy;
		this.weaponEnergy = maxWeaponEnergy;
		
		this.maxEngineEnergy = maxEngineEnergy;
		this.engineEnergy = maxEngineEnergy;
		
		this.weapons = createWeapons();
		this.engines = createEngines();
		this.hyperDrive = createHyperDrive();
		this.reactor = createReactor();
		this.sheild = createSheild();
	}

	protected abstract HyperDrive addHyperDrive();	
	protected HyperDrive createHyperDrive()
	{
		HyperDrive hyperDrive = addHyperDrive();
		
		Position centrePosition = new Position(0, 0, 0);
		DeltaPosition deltaAngle = new DeltaPosition(0, 0, position.theta());
		hyperDrive.move(centrePosition, deltaAngle);
		DeltaPosition deltaPosition = new DeltaPosition(position.x(), position.y(), 0);
		centrePosition = new Position(position);
		hyperDrive.move(centrePosition, deltaPosition);
		
		return hyperDrive;
	}
	
	protected abstract ArrayList<ShipEngine> addEngines();
	protected ArrayList<ShipEngine> createEngines()
	{
		ArrayList<ShipEngine> engines = addEngines();
		
		Position centrePosition = new Position(0, 0, 0);
		DeltaPosition deltaAngle = new DeltaPosition(0, 0, position.theta());
		for(ShipEngine engine : engines)
			engine.move(centrePosition, deltaAngle);
		
		DeltaPosition deltaPosition = new DeltaPosition(position.x(), position.y(), 0);
		centrePosition = new Position(position);
		for(ShipEngine engine : engines)
			engine.move(centrePosition, deltaPosition);
		
		return engines;
	}
	
	protected abstract Reactor addReactor();
	protected Reactor createReactor()
	{
		Reactor reactor = addReactor();
		Position centrePosition = new Position(0, 0, 0);
		DeltaPosition deltaAngle = new DeltaPosition(0, 0, position.theta());
		reactor.move(centrePosition, deltaAngle);
		DeltaPosition deltaPosition = new DeltaPosition(position.x(), position.y(), 0);
		centrePosition = new Position(position);
		reactor.move(centrePosition, deltaPosition);
		
		return reactor;
	}
	protected abstract Sheilds addSheild();
	
	protected Sheilds createSheild()
	{
		Sheilds sheild = addSheild();
		Position centrePosition = new Position(0, 0, 0);
		DeltaPosition deltaAngle = new DeltaPosition(0, 0, position.theta());
		sheild.move(centrePosition, deltaAngle);
		DeltaPosition deltaPosition = new DeltaPosition(position.x(), position.y(), 0);
		centrePosition = new Position(position);
		sheild.move(centrePosition, deltaPosition);
		
		return sheild;
	}
	
	protected abstract ArrayList<Weapon> addWeapons();
	
	protected ArrayList<Weapon> createWeapons()
	{
		ArrayList<Weapon> weapons = addWeapons();
		Position centrePosition = new Position(0, 0, 0);
		DeltaPosition deltaAngle = new DeltaPosition(0, 0, position.theta());
		for(Weapon weapon : weapons)
			weapon.move(centrePosition, deltaAngle);
		
		DeltaPosition deltaPosition = new DeltaPosition(position.x(), position.y(), 0);
		centrePosition = new Position(position);
		for(Weapon weapon : weapons)
			weapon.move(centrePosition, deltaPosition);
		
		return weapons;
	}
	
	protected double addHealth(double health)
	{
		this.health += health;
		this.health = HelperMethods.round2(this.health);
		if(this.health > maxHealth)
		{
			double extra = this.health - maxHealth;
			this.health = maxHealth;
			return extra;
		}
		return 0;
	}
	
	protected double damage(double damage)
	{
		double extraDamage = damage - this.health;
		if(extraDamage < 0)
			extraDamage = 0;
		
		this.health -= damage;
		if(this.health < 0)
			this.health = 0;
		
		this.health = HelperMethods.round2(this.health);
		return extraDamage;
	}

	protected double addWeaponEnergy(double energy)
	{
		this.weaponEnergy += energy;
		this.weaponEnergy = HelperMethods.round2(weaponEnergy);
		if(this.weaponEnergy > maxWeaponEnergy)
		{
			double extra = this.weaponEnergy - maxWeaponEnergy;
			this.weaponEnergy = maxWeaponEnergy;
			return extra;
		}
		return 0;
	}
	
	protected double removeWeaponEnergy(double energy)
	{
		double extraEnergy = energy - this.weaponEnergy;
		if(extraEnergy < 0)
			extraEnergy = 0;
		
		this.weaponEnergy -= energy;
		if(this.weaponEnergy < 0)
			this.weaponEnergy = 0;

		this.weaponEnergy = HelperMethods.round2(weaponEnergy);
		return extraEnergy;
	}
	
	protected double addEngineEnergy(double energy)
	{
		this.engineEnergy += energy;
		this.engineEnergy = HelperMethods.round2(engineEnergy);
		if(this.engineEnergy > maxEngineEnergy)
		{
			double extra = this.engineEnergy - maxEngineEnergy;
			this.engineEnergy = maxEngineEnergy;
			return extra;
		}
		return 0;
	}
	
	protected double removeEngineEnergy(double energy)
	{
		double extraEnergy = energy - this.engineEnergy;
		if(extraEnergy < 0)
			extraEnergy = 0;
		
		this.engineEnergy -= energy;
		if(this.engineEnergy < 0)
			this.engineEnergy = 0;

		this.engineEnergy = HelperMethods.round2(engineEnergy);
		return extraEnergy;
	}

	@Override
	protected void move()
	{	
		Position centrePosition = new Position(position);
		DeltaPosition deltaAngle = position.turn(rotateSpeed);
		for(Weapon weapon : weapons)
			weapon.move(centrePosition, deltaAngle);
		for(ShipEngine engine : engines)
			engine.move(centrePosition, deltaAngle);
		
		sheild.move(centrePosition, deltaAngle);
		reactor.move(centrePosition, deltaAngle);
		hyperDrive.move(centrePosition, deltaAngle);
		
		DeltaPosition deltaPosition = position.moveForwardConserveMomentum(speed);
		centrePosition = new Position(position);
		for(Weapon weapon : weapons)
			weapon.move(centrePosition, deltaPosition);
		for(ShipEngine engine : engines)
			engine.move(centrePosition, deltaPosition);
		
		sheild.move(centrePosition, deltaPosition);
		reactor.move(centrePosition, deltaPosition);
		hyperDrive.move(centrePosition, deltaPosition);
		
		if(HelperMethods.outOfBounds(position))
			position.stop();
	}
	
	protected void translate(double x, double y, double theta)
	{
		
	}
	
	@Override
	protected void collided(Ship otherShip)
	{
		
	}
	
	@Override
	protected void collided(Bullet bullet)
	{
		bullet.setToRemove();
	}
	
	@Override
	protected void collided(Weapon other)
	{
		
	}
	
	@Override
	public boolean tick()
	{
		move();
		for(Weapon weapon : weapons)
			weapon.tick();
		reactor.tick();
		distributeEnergy();
		if(shipPanel != null)
			shipPanel.update(health, sheild.Health(), engineEnergy);
		return false;
	}
	
	protected void distributeEnergy()
	{
		//will make it a priority list that can be ordered by player, so things recharges over other things
		
		//hyperdrive
		//can only be charged and used, otherwise any built up energy is dissipated back into the other systems
		//unless actually charging the hyperdrive will just return the energy sent to it
		double extra = hyperDrive.rechargeEnegry(reactor.getHyperdriveEnergy());
		
		//sheilds
		extra = sheild.rechargeEnegry(reactor.getSheildEnergy());

		//firing
		extra = addWeaponEnergy(reactor.getWeaponEnergy() + extra);
		
		//movement
		extra = addEngineEnergy(reactor.getMovementEnergy() + extra);
	}
	
	@Override
	public void setPanel(ActionListener actionListener, BottomPanel panel)
	{
//		panel.addObjectPanelBottomRight(new GameObjectPanel(actionListener, GameScreen.BOTTOM_WIDTH, GameScreen.BOTTOM_HEIGHT));
		if(shipPanel == null)
			this.shipPanel = new ShipPanel(actionListener, this.health, sheild.Health(), engineEnergy);
		panel.replacePanel(shipPanel);
	}
	
	@Override
	public InstanceObject hasClicked(Point point)
	{
		for(Weapon weapon : weapons)
			if(weapon.hasClicked(point) != null)
				return weapon;		
		if(reactor.hasClicked(point) != null)
			return reactor;
		if(super.hasClicked(point) != null)
			return this;
		if(sheild.hasClicked(point) != null)
			return sheild;
		return null;
	}
	
	@Override
	public void hasCollided(InstanceObject other)
	{
		if(sheild.hasHealth())
		{
			sheild.hasCollided(other);
			return;
		}
		for(Weapon weapon : weapons)
			weapon.hasCollided(other);
		for(ShipEngine engine : engines)
			engine.hasCollided(other);
		reactor.hasCollided(other);
		super.hasCollided(other);
	}
	
	@Override
	public void render(Graphics2D g)
	{
		super.render(g);
		
		for(Weapon weapon : weapons)
			weapon.render(g);
		
		for(ShipEngine engine : engines)
			engine.render(g);

		sheild.render(g);
		
		reactor.render(g);
		
		hyperDrive.render(g);
	}
	
	public ArrayList<Bullet> shoot()
	{
		ArrayList<Bullet> bullets = new ArrayList<Bullet>();		
		for(Weapon weapon : weapons)
		{
			if(this.weaponEnergy - weapon.cost() > 0)
			{
				ArrayList<Bullet> bulletsFired = weapon.shoot();
				if(!bulletsFired.isEmpty())
				{
					bullets.addAll(bulletsFired);
					this.weaponEnergy -= weapon.cost();
				}
			}
		}
		return bullets;
	}
	
	private boolean canMove()
	{
		double cost = 0;
		for(ShipEngine engine : this.engines)
			cost += engine.cost();
		if(cost < engineEnergy)
		{
			removeEngineEnergy(cost);
			return true;
		}
		return false;
	}
	
	private double speed()
	{
		double speed = 0;
		for(ShipEngine engine : engines)
			speed += engine.Speed();
		return speed;
	}
	
	private double rotateSpeed()
	{
		double speed = 0;
		for(ShipEngine engine : engines)
			speed += engine.RotateSpeed();
		return speed;
	}
	
	public void turnLeft(boolean down)
	{
		if(down && canMove())
			this.rotateSpeed = -rotateSpeed();
		else
			this.rotateSpeed = 0;
	}
	
	public void turnRight(boolean down)
	{
		if(down && canMove())
			this.rotateSpeed = rotateSpeed();
		else
			this.rotateSpeed = 0;
	}
	
	public void moveForward(boolean down)
	{
		if(down && canMove())
			this.speed = speed();
		else
			this.speed = 0;
	}
	
	public void moveBackwards(boolean down)
	{
		if(down && canMove())
			this.speed = -speed();
		else
			this.speed = 0;
	}
}
